"""
Copyright 2009 Texas A&M University
 
Licensed under the Apache License, Version 2.0 (the "License"); you may not use 
this file except in compliance with the License. You may obtain a copy of the 
License at

http://www.apache.org/licenses/LICENSE-2.0
  
Unless required by applicable law or agreed to in writing, software distributed 
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 
CONDITIONS OF ANY KIND, either express or implied. See the License for the 
specific language governing permissions and limitations under the License. 
"""

import zope.interface
from zope.interface.exceptions import DoesNotImplement

from meercat.interfaces import IResource, IInstance, IMetadata
from meercat.interfaces import IMutableResource, IMutableInstance
from meercat.base.resource import Resource
from meercat.base.instance import Instance
from meercat.storage.interfaces import IStorage



class MockInstance(Instance):
    """ A mock mutable instance """
    zope.interface.implements(IMutableInstance)
    
    def deactivate(self):
        """ Mark the object as no longer active """
        self._active = False
    
    def activate(self):
        """ Mark the object as active """
        self._active = True
    
    def append(self,metadata):
        """ Append new metadata to the instance """
        self._metadata.add(IMetadata(metadata))
    
    def remove(self,metadata):
        """ Remove a metadata object from the instance"""
        self._metadata.discard(metadata)
    
    def removeFromSource(self,source):
        """ Remove all metadata for the object from a given source """
        remove = filter(lambda x: x.getSource() == source,
                        self._metadata)
        for m in remove:
            self._metadata.remove(m)
    

class MockResource(Resource):
    """ A mock mutable resource """
    zope.interface.implements(IMutableResource)
    
    def __init__(self, *args, **kwargs):
        super(MockResource,self).__init__(*args, **kwargs)
        
        self._instances = set(map(lambda x: IMutableInstance(x),
                                  self._instances))
    
    def deactivate(self):
        """Iterate over the instances and mark them all as inactive.  As long 
        as a resource has one active instance, it is active.
        """
        for i in self._instances:
            i.deactivate()

    def append(self, instance):
        """Append an instance to the collection of instances for this resource
        """
        self._instances.add(IMutableInstance(instance))
    
    def remove(self, instance):
        """Remove a given instance from the collection of instances for this
        resource.  Return the removed instance.  Generally, removed instances
        must be appended to another resource before being saved.
        """
        self._instance.discard(instance)

class MockStorage(object):
    """ A mock storage """
    zope.interface.implements(IStorage)
    
    def __init__(self, name='mockStorage'):
        self._name = name
        self._resources = []
        self._irMap = {}
        self._instances = []
        self._lastid = 1
    
    
    def getName(self):
        """Return a name for the Storage"""
        return self._name
    
    def getNewId(self):
        self._lastid += 1
        return str(self._lastid)
    
    def action(self, instance):
        """This method performs an add/update operation on instance(s) as
        necessary.
        """
        if not IInstance.providedBy(instance):
            try:
                for i in instance:
                    if not IInstance.providedBy(i):
                        raise DoesNotImplement('MockStorage.action required '
                                               'objects that implement '
                                               'IInstance interface')
            except:
                raise DoesNotImplement('MockStorage.action required objects '
                                       'that implement IInstance interface')
        else:
            instance = [instance]
        
        for i in instance:
            if not IMutableInstance.providedBy(i):
                i = MockInstance(identifier=i.getIdentifier(),
                                 source=i.getSource(),
                                 active=i.isActive(),
                                 metadata=i.getMetadata(),
                                 modified=i.getLastModified())
            
            resource = self._irMap.get((i.getSource(),i.getIdentifier()),
                                       None)
            
            if resource:
                resource.remove(i)
                resource.append(i)
            else:
                self._lastid += 1
                resource = MockResource(identifier=self.getNewId(),
                                        instances=[i])
                self._irMap[(i.getSource(),i.getIdentifier())] = resource
                self._instances.append(i)
        
    
    def merge(self, destination, remove):
        """This method merges the remove object into the destination object.
        This operation is used when de-duping records. All of the instances from
        the remove resource should be appended to the destination object and then
        the remove object should be marked as inactive.
        
        Keyword Arguments:
        destination -- This must be an object that provides the 
          meercat.interfaces.IMutableResource interface.
        remove -- This is either an object providing the 
          meercat.interfaces.IResource interface or an iterable collection of 
          objects that provide the meercat.interfaces.IResource interface.
        
        If the destination argument is not a meercat.interfaces.IResource, a 
        zope.interface.exceptions.DoesNotImplement exception should be raised.
        
        If the remove argument is not a meercat.interfaces.IResource or a 
        collection of them, a zope.interface.exceptions.DoesNotImplement 
        exception should be raised.
        """
        raise NotImplementedError()
        if not IMutableResource.providedBy(remove):
            try:
                for r in remove:
                    if not IMutableResource.providedBy(r):
                        raise DoesNotImplement('MockStorage.merge requires '
                                               'objects that implement '
                                               'IMutableResource interface')
            except:
                raise DoesNotImplement('MockStorage.merge requires objects that '
                                       'implement IMutableResource interface')
        else:
            remove = [remove]
        
        if not IMutableResource.providedBy(destination):
            raise DoesNotImplement('MockStorage.merge requires objects that '
                                   'implement IMutableResource interface')
    
    def getResources(self, limit=None, offset=None, from_date=None,
                     until_date=None, source=None, identifier=None,
                     inactive=False):
        """Search through the storage and return an iterable collection of
        objects providing the meercat.interfaces.IResource interface.
        
        Keyword arguments:
        limit -- If provided, limit the resulting collection to this size
        offset -- If provided, only provide results after this offset into the 
          result set
        from_date -- If provided, limit results to those added/updated after 
          this datetime.datetime object
        until_date -- If provided, limit results to those added/update before
          this datetime.datetime object
        source -- If provided, limit the results to those that have instances
          coming from this source
        identifier -- If provided, only return the resource that matches this
          identifer
        inactive -- If false, limit results to 'active' resources
        """
        
        data = self._resources
        
        if from_date and until_date:
            data = filter(lambda x: from_date <= x.getLastModified() <= until_date,
                          data)
        elif from_date:
            data = filter(lambda x: from_date <= x.getLastModified(),
                          data)
        elif until_date:
            data = filter(lambda x: x.getLastModified() <= until_date,
                          data)
        
        if source:
            data = filter(lambda r: source in map(lambda i: i.getSource(),r),
                          data)
        
        if identifier:
            data = filter(lambda r: r.getIdentifier() == identifier, data)
        
        if not inactive:
            data = filter(lambda r: r.isActive(), data)
        
        if limit and offset:
            return data[offset:limit+offset]
        elif limit:
            return data[:limit]
        elif offset:
            return data[offset:]
        
        return data
  
    def getInstances(self, limit=None, offset=None, from_date=None,
                     until_date=None, source=None, identifier=None,
                     inactive=False):
        """Search through the storage and return an iterable collection of
        objects providing the meercat.interfaces.IInstance interface.
        
        Keyword arguments:
        limit -- If provided, limit the resulting collection to this size
        offset -- If provided, only provide results after this offset into the 
          result set
        from_date -- If provided, limit results to those added/updated after 
          this datetime.datetime object
        until_date -- If provided, limit results to those added/update before
          this datetime.datetime object
        source -- If provided, limit the results to those coming from this 
          source
        identifier -- If provided, only return the resource that matches this
          identifer
        inactive -- If false, limit results to 'active' resources
        """
        
        data = self._instances
        
        if from_date and until_date:
            data = filter(lambda x: from_date <= x.getLastModified() <= until_date,
                          data)
        elif from_date:
            data = filter(lambda x: from_date <= x.getLastModified(),
                          data)
        elif until_date:
            data = filter(lambda x: x.getLastModified() <= until_date,
                          data)
        
        if source:
            data = filter(lambda x: x.getSource() == source, data)
        
        if identifier:
            data = filter(lambda x: x.getIdentifier() == identifier, data)
        
        if not inactive:
            data = filter(lambda x: x.isActive(), data)
        
        if limit and offset:
            return data[offset:limit+offset]
        elif limit:
            return data[:limit]
        elif offset:
            return data[offset:]
        
        return data
    
    def getResourceCount(self):
        """Return the number of resources in the storage """
        return len(self._resources)
    
    def getInstanceCount(self):
        """Return the number of instances in the storage """
        return len(self._instances)
